// CBitrateShape.cpp: implementation of the CBitrateShape class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include <mmsystem.h>
#include "CBitrateShape.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

#define PACKET_HEADER_TCP					54
#define PACKET_FRAGMENT_SIZE_TCP			1460
#define PACKET_FRAGMENT_HEADER_TCP			54

#define PACKET_MINIMAL_UDP					60
#define PACKET_HEADER_UDP					42
#define PACKET_FRAGMENT_SIZE_UDP			1480
#define PACKET_FRAGMENT_HEADER_UDP			34

#define SHAPING_WINDOW_SIZE					10000.0

int CBitrateShape::nHighResolutionTiming = -1;
double CBitrateShape::dFrequency = 1000.0;
double CBitrateShape::dShapingWindowSize = SHAPING_WINDOW_SIZE;

CBitrateShape::CBitrateShape()
{
	dLastTime = 0.0;
	dDataBufferedByNow = 0.0;
	iTargetBPS = 500 * 1024 / 8;

	InitializeCriticalSection(&cs);

	if(nHighResolutionTiming < 0)
	{ // first time
		LARGE_INTEGER fr;
		if(nHighResolutionTiming = (int)QueryPerformanceFrequency(&fr))
		{
			dFrequency = (double)fr.QuadPart;
			QueryPerformanceCounter(&fr);
			dLastTime = (double)fr.QuadPart / dFrequency;
		}
		else
		{
			dLastTime = (double)timeGetTime() / 1000.0;
		}
	}
}

CBitrateShape::~CBitrateShape()
{
	DeleteCriticalSection(&cs);
}

void CBitrateShape::setTargetBitrate(float fTargetKbps)
{
	EnterCriticalSection(&cs);
	iTargetBPS = (int)(fTargetKbps * (1024. / 8.));
	LeaveCriticalSection(&cs);
}

void CBitrateShape::setTargetBitrate(int iTargetBPS)
{
	EnterCriticalSection(&cs);
	this->iTargetBPS = iTargetBPS;
	LeaveCriticalSection(&cs);
}

double CBitrateShape::getExactTime()
{
	if(nHighResolutionTiming > 0)
	{ // here is a high-resolution performance counter
		LARGE_INTEGER tm;
		QueryPerformanceCounter(&tm);
		return (double)tm.QuadPart / dFrequency;
	}
	else
	{ // here is no a high-resolution performance counter
		return (double)timeGetTime() / 1000.0;
	}
}

int CBitrateShape::getLengthOfDataToSend(int prot, int packetSize)
{
	int bytesToCount = packetSize;
	int i;

	switch(prot)
	{
		case PACKET_OVER_TCP:
			bytesToCount += PACKET_HEADER_TCP;
			i = packetSize / PACKET_FRAGMENT_SIZE_TCP - 1;
			if(i > 0)
				bytesToCount += i * PACKET_FRAGMENT_HEADER_TCP;
			break;

		case PACKET_OVER_UDP:
			bytesToCount += PACKET_HEADER_UDP;
			i = packetSize / PACKET_FRAGMENT_SIZE_UDP - 1;
			if(i > 0)
				bytesToCount += i * PACKET_FRAGMENT_HEADER_UDP;
			i = packetSize % PACKET_FRAGMENT_SIZE_UDP;
			if(i < (PACKET_MINIMAL_UDP - PACKET_HEADER_UDP))
				bytesToCount += (PACKET_MINIMAL_UDP - PACKET_HEADER_UDP) - i;
			break;

		default:
			break;
	}

	return bytesToCount;
}

void CBitrateShape::countUDPPacketData(int packetSize)
{
	EnterCriticalSection(&cs);
	dDataBufferedByNow += getLengthOfDataToSend(PACKET_OVER_UDP, packetSize);
	LeaveCriticalSection(&cs);
}

void CBitrateShape::countTCPPacketData(int packetSize)
{
	EnterCriticalSection(&cs);
	dDataBufferedByNow += getLengthOfDataToSend(PACKET_OVER_TCP, packetSize);
	LeaveCriticalSection(&cs);
}

BOOL CBitrateShape::isItTimeToSendByUDP(int* pMillis, int packetSize)
{
	return isItTimeToSend(pMillis, getLengthOfDataToSend(PACKET_OVER_UDP, packetSize));
}

BOOL CBitrateShape::isItTimeToSendByTCP(int* pMillis, int packetSize)
{
	return isItTimeToSend(pMillis, getLengthOfDataToSend(PACKET_OVER_TCP, packetSize));
}

BOOL CBitrateShape::isItTimeToSend(int* pMillis, int bytesToCount)
{
	int timeOut = 0;
	BOOL bres = TRUE;

	if(dLastTime == 0.0)
	{
		dLastTime = getExactTime();
	}
	else
//	if((dDataBufferedByNow + bytesToCount) > dShapingWindowSize)
	{
		EnterCriticalSection(&cs);
		double c_time = getExactTime();
		double delta = c_time - dLastTime;

		if(delta < 0)
			delta = 0;

		dDataBufferedByNow -= delta * iTargetBPS;
		dLastTime = c_time;

		if(dDataBufferedByNow < 0)
			dDataBufferedByNow = 0;

		if((dDataBufferedByNow + bytesToCount) > dShapingWindowSize)
		{
			timeOut = (int)((dDataBufferedByNow - dShapingWindowSize + bytesToCount) * 1000.0 / iTargetBPS);
			bres = FALSE;
		}
		LeaveCriticalSection(&cs);
	}
	*pMillis = timeOut;
	return bres;
}
